#region File Header
//-----------------------------------------------------------------------------
// SibLib
//
// Copyright (C) 2010 Julien Villers
// This program is distributed under the terms of the 
// GNU Lesser General Public License (LGPL).
// See Docs/lgpl.txt and Docs/gpl.txt
//-----------------------------------------------------------------------------
#endregion

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace SibLib.Render.Primitives3D
{
    /// <summary>
    /// Geometric primitive class for drawing cylinders with both extremities being half-spheres.
    /// </summary>
    public class RoundedCylinderPrimitive : GeometricPrimitive
    {
        /// <summary>
        /// Default settings provided.
        /// </summary>
        /// <param name="graphicsDevice"></param>
        public RoundedCylinderPrimitive(GraphicsDevice graphicsDevice)
            : this(graphicsDevice, 1, 1, 32)
        {
        }

        /// <summary>
        /// All parameters must be set here.
        /// </summary>
        /// <param name="graphicsDevice"></param>
        /// <param name="height"></param>
        /// <param name="diameter"></param>
        /// <param name="tesselation"></param>
        public RoundedCylinderPrimitive(GraphicsDevice graphicsDevice,
                                        float height, float diameter, int tessellation)
        {
            if (tessellation < 3)
                throw new ArgumentOutOfRangeException("tessellation");

            float radius = diameter / 2;
            float cylinderHeight = (height / 2) - (radius);

            // TODO: massive copy/paste from Cylinder, would be nice, maybe, to share code.

            int tessellation2 = tessellation * 2;

            // Create a ring of triangles around the outside of the cylinder.
            for (int i = 0; i < tessellation; i++)
            {
                Vector3 normal = CylinderPrimitive.GetCircleVector(i, tessellation);

                AddVertex(normal * radius + Vector3.Up * cylinderHeight, normal);
                AddVertex(normal * radius + Vector3.Down * cylinderHeight, normal);

                int i2 = i * 2;
                AddIndex(i2);
                AddIndex(i2 + 1);
                AddIndex((i2 + 2) % tessellation2);

                AddIndex(i2 + 1);
                AddIndex((i2 + 3) % tessellation2);
                AddIndex((i2 + 2) % tessellation2);
            }

            // Create flat triangle fan caps to seal the top and bottom.
            Vector3 topCap = new Vector3(0.0f, cylinderHeight, 0.0f);
            CreateCap(tessellation, topCap, radius);
            CreateCap(tessellation, -topCap, radius);

            InitializePrimitive(graphicsDevice);
        }

        /// <summary>
        /// Helper method creates a half-sphere to close the ends of the cylinder.
        /// </summary>
        void CreateCap(int tessellation, Vector3 center, float radius)
        {
            int verticalSegments = tessellation;
            int horizontalSegments = tessellation;

            int baseVertexIndex = CurrentVertex;

            // Start with a single vertex at the bottom of the sphere.
            AddVertex((Vector3.Down * radius) + center, Vector3.Down);

            float latIncrement = MathHelper.PiOver2 / verticalSegments;
            float longIncrement = MathHelper.TwoPi / horizontalSegments;

            int baseLat = (center.Y < 0) ? 2 : verticalSegments - 1;

            // Create rings of vertices at progressively higher latitudes.
            for (int i = 0; i < verticalSegments - 1; i++)
            {
                float latitude = ((i + baseLat) * latIncrement) - MathHelper.PiOver2;

                float dy = (float)Math.Sin(latitude);
                float dxz = (float)Math.Cos(latitude);

                // Create a single ring of vertices at this latitude.
                for (int j = 0; j < horizontalSegments; j++)
                {
                    float longitude = j * longIncrement;

                    float dx = (float)Math.Cos(longitude) * dxz;
                    float dz = (float)Math.Sin(longitude) * dxz;

                    Vector3 normal = new Vector3(dx, dy, dz);

                    Vector3 vertex = (normal * radius) + center;
                    AddVertex(vertex, normal);
                }
            }

            // Finish with a single vertex at the top of the sphere.
            AddVertex((Vector3.Up * radius) + center, Vector3.Up);

            // Create a fan connecting the bottom vertex to the bottom latitude ring.
            if (center.Y < 0)
            {
                for (int i = 0; i < horizontalSegments; i++)
                {
                    AddIndex(baseVertexIndex);
                    AddIndex(baseVertexIndex + 1 + (i + 1) % horizontalSegments);
                    AddIndex(baseVertexIndex + 1 + i);
                }
            }

            // Fill the sphere body with triangles joining each pair of latitude rings.
            for (int i = 0; i < verticalSegments - 2; i++)
            {
                for (int j = 0; j < horizontalSegments; j++)
                {
                    int nextI = i + 1;
                    int nextJ = (j + 1) % horizontalSegments;

                    AddIndex(baseVertexIndex + 1 + i * horizontalSegments + j);
                    AddIndex(baseVertexIndex + 1 + i * horizontalSegments + nextJ);
                    AddIndex(baseVertexIndex + 1 + nextI * horizontalSegments + j);

                    AddIndex(baseVertexIndex + 1 + i * horizontalSegments + nextJ);
                    AddIndex(baseVertexIndex + 1 + nextI * horizontalSegments + nextJ);
                    AddIndex(baseVertexIndex + 1 + nextI * horizontalSegments + j);
                }
            }

            if (center.Y > 0)
            {
                for (int i = 0; i < horizontalSegments; i++)
                {
                    AddIndex(CurrentVertex - 1);
                    AddIndex(CurrentVertex - 2 - (i + 1) % horizontalSegments);
                    AddIndex(CurrentVertex - 2 - i);
                }
            }
        }
    }
}